ReactからCognitoオーソライザーを使ったAPI Gatewayをコールする
情報システム室 進地 です。
ReactからCognitoオーソライザーを設定したAPI Gateway + Lambdaをコールしてみました。 XHRのライブラリにはaxiosを使っています。
Backend
AWS CDKコードサンプル
import { aws_lambda, Stack, StackProps, Duration } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; import { ManagedPolicy, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam"; import { Cors, EndpointType, LambdaIntegration, MethodLoggingLevel, RestApi, CognitoUserPoolsAuthorizer } from "aws-cdk-lib/aws-apigateway"; import * as cognito from 'aws-cdk-lib/aws-cognito'; import * as dotenv from 'dotenv'; dotenv.config(); export class SomeStack extends Stack { constructor(scope: Construct, id: string, props: StackProps) { super(scope, id, props); const lambdaRole = new Role(this, "SomeLambdaRole", { assumedBy: new ServicePrincipal("lambda.amazonaws.com"), managedPolicies: [ ManagedPolicy.fromManagedPolicyArn( this, "lambda", "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ) ], description: "Basic Lambda Role" }); const some = new PythonFunction(this, 'SomeHandler', { functionName: 'SomeHandler', runtime: aws_lambda.Runtime.PYTHON_3_11, entry: 'lambda/some', handler: 'lambda_handler', role: lambdaRole, timeout: Duration.seconds(30) }); const someIntegration = new LambdaIntegration(some); const apigw = new RestApi(this, "apigw", { restApiName: "apigw", deployOptions: { loggingLevel: MethodLoggingLevel.INFO, dataTraceEnabled: true, metricsEnabled: true, }, defaultCorsPreflightOptions: { allowOrigins: Cors.ALL_ORIGINS, allowMethods: Cors.ALL_METHODS, allowHeaders: Cors.DEFAULT_HEADERS, statusCode: 200, }, endpointTypes: [EndpointType.REGIONAL], cloudWatchRole: true }); // 既存のユーザープールを使う const userPool = cognito.UserPool.fromUserPoolArn( this, 'ExistingUserPool', `arn:aws:cognito-idp:${process.env.AWS_REGION}:${process.env.AWS_ACCOUNT_ID}:userpool/${process.env.COGNITO_USER_POOL_ID}` ); // Cognitoのユーザープールにクライアントを追加 const userPoolClientName = 'SomeUserPoolClient'; userPool.addClient(userPoolClientName, { userPoolClientName: userPoolClientName, // パスワード認証を有効にするためには下記の設定が必要 authFlows: { userPassword: true }, }); // CognitoのAuthorizerの作成 const cognitoAuthorizer = new CognitoUserPoolsAuthorizer( this, 'cognitoAuthorizer', { cognitoUserPools: [userPool], } ); const someEndpoint = apigw.root.addResource("some"); someEndpoint.addMethod( "POST", someIntegration, { authorizer: cognitoAuthorizer } ); } }
LambdaはPythonを使っています。
Cognitoユーザプールクライアントを作成する際に、パスワード認証を有効にするためにauthFlows: { userPassword: true }
を設定しています(ハイライト部)。
.envファイルは下記のように設定しています。
COGNITO_USER_POOL_ID=<接続するCognitoユーザプールIDを設定する> AWS_ACCOUNT_ID=<AWSアカウントIDを設定する> AWS_REGION=ap-northeast-1
Lambdaコードサンプル
someParameter
というキーで受け取ったパラメータをそのまま返すLambdaとします。
#!/usr/bin/env python # -*- coding: utf-8 -*- import os import logging import json # ロギング用 logging.basicConfig(format='%(asctime)s - %(threadName)s - %(module)s:%(funcName)s(%(lineno)d) - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) def lambda_handler( event: dict, context: dict ) -> dict: """ 実質メイン処理 """ logger.info("処理開始 [__PROGRESS__]") param = json.loads(event["body"]) statusCode = 200 body = { "someParameter": param.get("someParameter") } logger.info("処理終了 [__PROGRESS__]") return { "statusCode": statusCode, "headers": {"Access-Control-Allow-Origin": "*"}, "body": json.dumps(body) } if __name__ == "__main__": event = { } lambda_handler(event, {})
Frontend
Reactコードサンプル
/* Cognitoオーソライザーを設定したAPI Gateway + Lambdaをコールする */ const callAPI = async () => { try { /* トークンを取得 */ const token = await getCognitoAccessToken( process.env.REACT_APP_COGNITO_URL, 'USER_PASSWORD_AUTH', process.env.REACT_APP_COGNITO_USER_POOL_CLIENT_ID, process.env.REACT_APP_COGNITO_USERNAME, process.env.REACT_APP_COGNITO_PASSWORD ); /* APIに渡したいパラメータを設定 */ const requestPayload = { 'someParameter': '<適当な値を渡す>' }; /* ヘッダ設定 */ const requestHeaders = { 'Content-Type': 'application/json', 'Authorization': token }; const requestOptions = { 'headers' : requestHeaders }; /* APIを呼び出し */ const requestUrl = '<APIのエンドポイントのURL>'; try { const response = await axios.post(requestUrl, JSON.stringify(requestPayload), requestOptions); const content = response.data; console.log(content.someParameter); } catch(err) { console.error(err); } } catch(err) { console.error(err); } }; /* アクセストークン取得用 */ const getCognitoAccessToken = async (cognitoUrl, authFlow, clientId, username, password) => { // リクエストボディ const payload = { AuthFlow: authFlow, ClientId: clientId, AuthParameters: { USERNAME: username, PASSWORD: password } }; // リクエストヘッダー const headers = { 'Content-Type': 'application/x-amz-json-1.1', 'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth' } // リクエストオプション const options = { 'headers' : headers } const response = await axios.post(cognitoUrl, JSON.stringify(payload), options); const content = response.data; return content.AuthenticationResult.IdToken; }
Cognito接続用のユーザをあらかじめユーザプールに作成して、.envに設定しておきます。 Frontendの.envは次のように設定しています。
REACT_APP_COGNITO_USER_POOL_CLIENT_ID=<CognitoユーザプールクライアントID> REACT_APP_COGNITO_URL=https://cognito-idp.ap-northeast-1.amazonaws.com/ REACT_APP_COGNITO_USERNAME=<Cognito接続用ユーザのユーザ名> REACT_APP_COGNITO_PASSWORD=<Cognito接続用ユーザのパスワード>
callAPI関数を呼び出せば、Backendで作成したAPI Gateway + Lambdaをコールして、レスポンスを取得できます。
留意事項
今回のサンプルでは、Cognito接続用のユーザパスワードをReact側に設定していますが、実際に運用に載せる場合はパスワードなどCredentialな情報は.envに記載しないでください。
具体的には、Proxy ServerをVercelなどで作って、FrontendでCredentialを持たないようにしてください。